package gml.android.osc;


import gml.android.hellosc.R;

import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Date;

import com.illposed.osc.OSCListener;
import com.illposed.osc.OSCMessage;
import com.illposed.osc.OSCPacket;
import com.illposed.osc.OSCPort;
import com.illposed.osc.OSCPortIn;
import com.illposed.osc.OSCPortOut;

import android.content.Context;
import android.util.Log;

/**
 * Class Adapter who works as a server, sending OSC messages :)
 * We trying to implement an Listener to listen the connected client
 * (an application made in SuperCollider, PureData, Max/MSP, CSound, etc...)
 * 
 * This Class have issues to work on Android:
 * - Suport to LogCat ({@link Log} is active by default)
 * 
 * @author andreas, gcravista.gmail.dot.com
 * @see {@link OSCPort}, {@link OSCPortIn}, {@link Connection}
 * @version 0.1
 */
public class OSCConnection implements Connection {

		/** Special IP; used only during development of your own {@link Context}/{@link Activity}
		 * @value 10.0.2.2*/
		public static final String IP_EMULATOR = "10.0.2.2";
		
		/** Special Port; use only during development of your own {@link Context}/{@link Activity}
		 * if your emulator machine have other value (i.e 5556, 5558), change this value or make another const
		 * @value 5554
		 * @see <ul>{@link http://developer.android.com/guide/developing/tools/adb.html}</ul>*/
		public static final Integer PORT_ADB_CONSOLE = 5554;
		
		/** Special Port; use only during development of your own {@link Context}/{@link Activity}
		 * if your emulator machine have other value (i.e 5557, 5559), change this value or make another const
		 * @value 5554
		 * @see <ul>{@link http://developer.android.com/guide/developing/tools/adb.html}</ul>*/
		public static final Integer PORT_ADB_EMULATOR = 5555;
		
		/** An random port to PureData Patch */
		public static final Integer OSC_PORT_PD = 11002;
		
		/** OSC address of server side of SuperCollider
		 * @see http://supercollider.sourceforge.net/ for more info*/
		public static final Integer OSC_PORT_SUPERCOLLIDER = 57120;
		
		/** Holds IP of server*/
		private String ipClient = null;
		
		/** Holds Port of client to connect*/
        private Integer portClient = null;
        
        /** hold an port of  messages to this server, you must set the IP of 
         * {@link OSCPortOut} of client with IP of this device; the port of listener is of desired client*/
        private Integer portListener;
        
        private OSCPortOut sender = null;
        
        /** hold a actual message incoming*/
		protected static Object[] incomingMessage;
	
		private boolean connected;		
		
		/** create an empty {@link OSCConnection}*/
        public OSCConnection() { }
      
        /** This constructor set IP/port of client */
        public OSCConnection(String ipClient, int portClient){
        	this.ipClient = ipClient;
        	this.portClient = portClient;
        }
        
        /** This constructor set IP/port of client and the port that server listen client */
        public OSCConnection(String ipClient, int portClient, int portListener){
        	this.ipClient = ipClient;
        	this.portClient = portClient;
        	this.portListener = portListener;
        }
        
        /** Connect OSC; the {@link Context} argument suply information about which
         * context will try make the OSC connection, and get an specific resource to
         * send send "hello client" in specific OSC address ({@link R.string.osc_root})
         * @param Context that making the OSC connection
         * @see {@link OSCPort}, {@link OSCPortOut}*/
        public void connect(Context context){
        	//values setted, now try connect
        	try {
        		//creating server
        		sender = new OSCPortOut(InetAddress.getByName(getIpClient()), getPortClient());
        		if(sender!=null){
        			//if sender ok:
        			connected = CONNECTED;
        			//Sending notification to Log and client...
        			Log.i(TAG, "sending message to "+getIpClient()+":"+getPortClient()+":"+context.getString(R.string.osc_root));
        			send(context.getString(R.string.osc_root), "hello client");
        		}
        		else{
        			connected = DISCONNECTED;
        		}	
        	} 
        	catch (SocketException se) {
        		Log.e(TAG, "	verify IP: "+getIpClient());
        		Log.e(TAG, "	verify Port: "+getPortClient());
        		Log.e(TAG,"		Not be possible to connect", se);
        	} catch (UnknownHostException ue) {
        		Log.e(TAG, "	verify IP: "+getIpClient());
        		Log.e(TAG, "	verify Port: "+getPortClient());
        		Log.e(TAG,"		Not be possible to connect", ue);
        	}
        }

	
		@Override
        public Boolean isConnected() {
			Log.i(TAG, connected?"OSC connected":"OSC not connected");
			return this.connected;		
        }

		/** Send a {@link Object} array to client, assigned in moment of instatiation of this {@link OSCConnection}*/
        public void send(String oscAddr, Object[] args) {
        	if (oscAddr != null) {
        		OSCMessage msg = new OSCMessage(oscAddr, args);
        		try {
        			sender.send((OSCPacket) msg);
        			Log.i(TAG, "message sent to "+oscAddr+" at "+getIpClient()+", port"+getPortClient());
        		} catch (Exception e) {
        			Log.e("OSCConnection.send()", "Couldn't send to "+oscAddr+" at "+ipClient, e);
        		}
        	}
        }
        
        /** Send a {@link String} to client, assigned in moment of instatiation of this {@link OSCConnection}*/
        public void send(String oscAddr, String arg) {
            send(oscAddr, new Object[] {arg});
        }
        
        /** Send a {@link Integer} to client assigned in moment of instatiation of this {@link OSCConnection}*/
        public void send(String oscAddr, Integer arg) {
            send(oscAddr, new Object[] {arg});
        }
        
        /** Send a {@link Long} to client, assigned in moment of instatiation of this {@link OSCConnection}*/
        public void send(String oscAddr, Long arg) {
            send(oscAddr, new Object[] {arg});
        }
        
        /** Send a {@link Float} to client assigned in moment of instatiation of this {@link OSCConnection}*/
        public void send(String oscAddr, Float arg) {
            send(oscAddr, new Object[] {arg});
        }
        
        /** Send a {@link Float} to client, assigned in moment of instatiation of this {@link OSCConnection}*/
        public void send(String oscAddr, Double arg) {
            send(oscAddr, new Object[] {arg});
        }
        
        /** Send a {@link Boolean} to client assigned in moment of instatiation of this {@link OSCConnection}*/
        public void send(String oscAddr, Boolean arg) {
            send(oscAddr, new Object[] {arg});
        }
        
		/**
		 * @return the ipServer
		 */
		public String getIpClient() {
			return ipClient;
		}

		/**
		 * @param ip the ipServer to set
		 */
		public void setIpClient(String ip) throws Exception{
			if(!isConnected()){
				this.ipClient = ip;
			}
			else{
				Log.e(TAG, "Cannot set IP of Sender because sender is already connected");
				throw new Exception();
			}
		}

		/**
		 * @return the portClient
		 */
		public Integer getPortClient() {
			return portClient;
		}

		/**
		 * @param portClient the portClient to set
		 */
		public void setPortClient(Integer portClient) throws Exception {
			if(!isConnected()){
				this.portClient = portClient;
			}
			else{
				Log.e(TAG, "Cannot set Port of Sender because sender is already connected");
				throw new Exception();
			}
		}
		
		/** @return the listening port*/
		public int getListeningPort() {
			// TODO Auto-generated method stub
			return portListener;
		}
		
		/** @param the listening port*/
		public void setListeningPort(int port) {
			// TODO Auto-generated method stub
			this.portListener = port;
		}

		/*
		 * (non Java-doc)
		 * FIXME: why onnection not occurs??
		 */
		public void receive(String address){		
			try {
				OSCPortIn receiver = new OSCPortIn(getListeningPort());
				OSCListener listener = new OSCListener() {

					@Override
					public void acceptMessage(Date time, OSCMessage message) {
						Log.i(TAG, "Message received from "+message.getAddress());
					}
				};
				
				receiver.addListener(address, listener);
				receiver.startListening();
				Thread.sleep(100);
				receiver.stopListening();
			} catch (InterruptedException ie) {
				Log.e(TAG, "", ie);
			} catch (SocketException se) {
				Log.e(TAG, "", se);
			}
		}
		
}
